//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/builtins/builtins.h"
#include "sagittarius/session/session.h"
#include "sagittarius/io/io.h"
#include "sagittarius/parser/pAST.h" //sg_getOpPrec
#include "sagittarius/backend/llvm/support.h"

#include <malloc.h>
#include <string.h>

#include <string>
#include <sstream>

char* sg_network_repr(sg_network_val* net) {
    std::string rep = Sagittarius::sg_network_str(net);
    char* buf;
    
    size_t len = strlen(rep.c_str());
    
    buf = (char*)malloc(len+1);
    memcpy(buf, rep.c_str(), len+1);
    
    return buf;
}

namespace Sagittarius {

std::string sg_network_str(sg_network_val* net) {
    AT(net->t == (sg_type*)sg_global_session()->bt->network, "Unexpected type");
    
    return "network(" + sg_reaction_rules_str(net) + ")";
}

std::string sg_reaction_rules_str(sg_network_val* net) {
    AT(net->t == (sg_type*)sg_global_session()->bt->network, "Unexpected type");
    
    std::string str;
    sg_list_iter_val* iter = sg_list_get_iter(net->rules);
    
    sg_value* val;
    sg_reaction_rule_val* rule = sg_cast_to_reaction_rule(sg_list_iter_deref(iter));
    
    while(rule) {
        str += sg_reaction_rule_str(rule);
        
        sg_list_iter_next(iter);
        val = sg_list_iter_deref(iter);
        if(val)
            rule = sg_cast_to_reaction_rule(val);
        else rule = NULL;
    }
    
    return str;
}

std::string sg_reaction_rule_str(sg_reaction_rule_val* rule) {
    AT(rule->t == (sg_type*)sg_global_session()->bt->rxn_rule, "Unexpected type");
    return  sg_node_str(rule->rxn, sg_getOpPrec(rule->rxn->type)) 
            + ", " + sg_node_str(rule->rate, sg_getOpPrec(rule->rate->type)) + ";\n";
}

std::string sg_node_str(sg_node_val* node, sg_OpPrec prec, int dump_symtype) {
    AT(node->t == (sg_type*)sg_global_session()->bt->node, "Unexpected type");
    
    switch(node->type) {
        case PAST_NODE_TYPE_SYMBOL: {
            sg_name_val* name = node->name;
            AN(name, "Expected name");
            std::string str(sg_name_from_sym(name->s, sg_global_session()->st));
            
            if(dump_symtype && node->obj) {
                #if SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_MINGW
                std::stringstream ss;
                ss << sg_get_real_scalar(node->obj);
                str += "(" + ss.str() + ")";
                #else
                str += "(" + std::to_string(sg_get_real_scalar(node->obj)) + ")";
                #endif
            }
            if(dump_symtype) {
                switch(name->sym_tp) {
                    case SG_SYMTP_NORMAL:
                        break;
                    case SG_SYMTP_SPECIES:
                        str += "[SPECIES]";
                        break;
                    case SG_SYMTP_RATE_LAW_CONST:
                        str += "[INFERRED]";
                        break;
                    default:
                        AN(0, "Unknown symbol type");
                }
            }
            return str;
        }
        case PAST_NODE_TYPE_RXN:
            AN(node->a, "Empty lhs");
            AN(node->b, "Empty rhs");
            return sg_node_str(node->a, SG_OP_ADDSUB, dump_symtype) + " → " + sg_node_str(node->b, SG_OP_ADDSUB, dump_symtype);
        case PAST_NODE_TYPE_ADD: {
            AN(node->a, "Empty lhs");
            AN(node->b, "Empty rhs");
            std::string str;
            if(SG_OP_ADDSUB < prec)
                str += "(";
            str += sg_node_str(node->a, SG_OP_ADDSUB, dump_symtype) + " + " + sg_node_str(node->b, SG_OP_ADDSUB, dump_symtype);
            if(SG_OP_ADDSUB < prec)
                str += ")";
            return str;
        }
        case PAST_NODE_TYPE_SUB: {
            AN(node->a, "Empty lhs");
            AN(node->b, "Empty rhs");
            std::string str;
            if(SG_OP_ADDSUB < prec)
                str += "(";
            str += sg_node_str(node->a, SG_OP_ADDSUB, dump_symtype) + " - " + sg_node_str(node->b, SG_OP_ADDSUB, dump_symtype);
            if(SG_OP_ADDSUB < prec)
                str += ")";
            return str;
        }
        case PAST_NODE_TYPE_MUL: {
            AN(node->a, "Empty lhs");
            AN(node->b, "Empty rhs");
            std::string str;
            if(SG_OP_DIVMUL < prec)
                str += "(";
            str += sg_node_str(node->a, SG_OP_DIVMUL, dump_symtype) + "*" + sg_node_str(node->b, SG_OP_DIVMUL, dump_symtype);
            if(SG_OP_DIVMUL < prec)
                str += ")";
            return str;
        }
        case PAST_NODE_TYPE_DIV: {
            AN(node->a, "Empty lhs");
            AN(node->b, "Empty rhs");
            std::string str;
            if(SG_OP_DIVMUL < prec)
                str += "(";
            str += sg_node_str(node->a, SG_OP_DIVMUL, dump_symtype) + "/" + sg_node_str(node->b, SG_OP_DIVMUL, dump_symtype);
            if(SG_OP_DIVMUL < prec)
                str += ")";
            return str;
        }
        default:
            AN(0, "Unkown node type");
    }
}

}
